/*!
// DatatypeExp v1.0
// Copyright 2015 OrChange Inc. All rights reserved.
// Licensed under the MIT License.
*/
/*jslint vars: true, sloppy: true, nomen: true, plusplus: true */
/*global Exp: false, expPattern: false */

/**
 * Number pattern expression object.
 *
 * @class NumberExp
 * @constructor
 * @param {string} exp
 */
expPattern.numberExpPattern = /^(#|\+)([0-9,\-><=]+)?$/;
var greaterPattern = /^(>|>=)(\-?\d+)$/;
var lessPattern = /^(<|<=)(\-?\d+)$/;
var rangePattern = /^(\-?[\d]+)\-(\-?[\d]+)$/;
var enumPattern = /^\-?\d+$/;

var NumberExp = function NumberExp(exp) {
	Exp.apply(this, arguments);

	if (!expPattern.numberExpPattern.test(exp)) {
		throw new SyntaxError('Expression: "' + exp + '" is not a valid number pattern!');
	}

	this.$$greater = null;
	this.$$less = null;
	this.$$range = [];
	this.$$enum = [];

	this.$$isInverseMode = false;

	this.$initialization();
};
NumberExp.prototype = new Exp();
NumberExp.prototype.constructor = NumberExp;

NumberExp.prototype.$initialization = function () {
	if (this.exp === '#') {
		return;
	}

	if (this.exp.charAt(0) === '+') {
		this.$$isInverseMode = true;
	}

	var expList = this.$getExpList();

	this.$setGreater(expList.GREATER);
	this.$setLess(expList.LESS);
	this.$setRange(expList.RANGE);
	this.$setEnum(expList.ENUM);
};

NumberExp.prototype.$setGreater = function (greaterExp) {
	if (greaterExp === null) {
		return this;
	}

	var syntax = greaterExp.match(greaterPattern);
	this.$$greater = {
		value: Number(syntax[2]),
		withEqual: syntax[1] === '>='
	};

	return this;
};

NumberExp.prototype.$setLess = function (lessExp) {
	if (lessExp === null) {
		return this;
	}

	var syntax = lessExp.match(lessPattern);
	this.$$less = {
		value: Number(syntax[2]),
		withEqual: syntax[1] === '<='
	};

	return this;
};

NumberExp.prototype.$setEnum = function (enumExpList) {
	enumExpList.forEach(function (value, index) {
		this.push(Number(value));
	}, this.$$enum);

	return this;
};

NumberExp.prototype.$setRange = function (rangeExpList) {
	rangeExpList.forEach(function (value, index) {
		var syntax = value.match(rangePattern);
		var low = Number(syntax[1]), high = Number(syntax[2]);

		if (low > high) {
			throw new Error('Wrong range');
		}

		this.push({
			low: low,
			high: high
		});
	}, this.$$range);

	return this;
};

NumberExp.prototype.$testGreaterThan = function (number) {
	if (this.$$greater === null) {
		return false;
	}

	var withEqual = this.$$greater.withEqual, value = this.$$greater.value;

	return withEqual ? number >= value : number > value;
};

NumberExp.prototype.$testLessThan = function (number) {
	if (this.$$less === null) {
		return false;
	}

	var withEqual = this.$$less.withEqual, value = this.$$less.value;

	return withEqual ? number <= value : number < value;
};

NumberExp.prototype.$testRange = function (number) {
	var i, len = this.$$range.length;
	if (!len) {
		return false;
	}


	for (i = 0; i < len; i++) {
		if (number < this.$$range[i].high && number > this.$$range[i].low) {
			return true;
		}
	}

	return false;
};

NumberExp.prototype.$testEnum = function (number) {
	var i, len = this.$$enum.length;
	if (!len) {
		return false;
	}

	for (i = 0; i < len && this.$$enum[i] !== number; i++) {}
	return this.$$enum[i] === number;
};

NumberExp.prototype.$getExpList = function () {
	var i, expList = {
		GREATER: null,
		LESS: null,
		RANGE: [],
		ENUM: []
	};

	this.exp.substr(1).split(',').forEach(function (value, inedex) {
		if (lessPattern.test(value)) {
			this.LESS = value;
		} else if (greaterPattern.test(value)) {
			this.GREATER = value;
		} else if (rangePattern.test(value)) {
			this.RANGE.push(value);
		} else if (enumPattern.test(value)) {
			this.ENUM.push(value);
		} else {
			throw new Error('');
		}
	}, expList);

	return expList;
};

NumberExp.prototype.$test = function (number) {
	// Test basic datatype.
	if (typeof number !== 'number') {
		return false;
	}

	if (this.exp === '#') {
		return true;
	}

	var isMatched = this.$testLessThan(number) ||
		this.$testGreaterThan(number) ||
		this.$testRange(number) ||
		this.$testEnum(number);

	return this.$$isInverseMode ? !isMatched : isMatched;
};
